#include <Naipax/Common.hpp>
#include <Naipax/Error.hpp>
#include <Naipax/Fnv.hpp>
#include <cstring>

using namespace Naipax::Internal;

void Fnv::Hash128(const char *pData, uint32_t DataLen, void *pDigest) {
    if (pData == NULL || pDigest == NULL) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }

    // Initial hash value: CF470AAC 6CB293D2 F52F88BF 32307F8F
    struct {
        uint64_t mWord[2];
    } HashVal;

    HashVal.mWord[0] = 0xF52F88BF32307F8FULL;
    HashVal.mWord[1] = 0xCF470AAC6CB293D2ULL;
    
    // If DataLen is zero, then hashing is not needed.
    if (DataLen == 0) {
        memcpy(pDigest, HashVal.mWord, FNV_128_DIGEST_SIZE);
        return;
    }
    
    uint64_t Val[4];

    Val[0] = (HashVal.mWord[0] & 0x00000000ffffffffULL);
    Val[1] = (HashVal.mWord[0] >> 32);
    Val[2] = (HashVal.mWord[1] & 0x00000000ffffffffULL);
    Val[3] = (HashVal.mWord[1] >> 32);

    // 128bit prime = 00000000 01000000 00000000 00000159
    static const uint32_t FNV_128_PRIME_LOW = 0x159;
    static const uint32_t FNV_128_PRIME_SHIFT = 20;

    uint32_t Pos = 0;

    while (Pos < DataLen) {
        Val[0] ^= (uint64_t) *pData++;
        ++Pos;

        // Multiplication
        uint64_t Tmp[4];

        Tmp[0] = Val[0] * FNV_128_PRIME_LOW;
        Tmp[1] = Val[1] * FNV_128_PRIME_LOW;
        Tmp[2] = Val[2] * FNV_128_PRIME_LOW;
        Tmp[3] = Val[3] * FNV_128_PRIME_LOW;

        Tmp[2] += Val[0] << FNV_128_PRIME_SHIFT;
        Tmp[3] += Val[1] << FNV_128_PRIME_SHIFT;

        // Propagate carries
        Tmp[1] += (Tmp[0] >> 32);
        Val[0] = Tmp[0] & 0x00000000ffffffffULL;

        Tmp[2] += (Tmp[1] >> 32);
        Val[1] = Tmp[1] & 0x00000000ffffffffULL;

        Val[3] = Tmp[3] + (Tmp[2] >> 32);
        Val[2] = Tmp[2] & 0x00000000ffffffffULL;
    }

    HashVal.mWord[1] = ((Val[3]<<32) | Val[2]);
    HashVal.mWord[0] = ((Val[1]<<32) | Val[0]);

    memcpy(pDigest, HashVal.mWord, FNV_128_DIGEST_SIZE);
}
